﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;
using System.Globalization;

namespace YasharEl.Infrastructure.ExceptionHandling
{
    /// <summary>
    /// Represents an entry in an <see cref="ExceptionPolicy"/> containing
    /// an exception type as the key and a list of 
    /// <see cref="IExceptionHandler"/> objects as the value.
    /// </summary>
    public sealed class ExceptionPolicyEntry
    {
        #region ReadOnly Fields

        private readonly PostHandlingAction postHandlingAction;
        private readonly IEnumerable<IExceptionHandler> handlers;

        #endregion

        #region Fields

        private string policyName = string.Empty;
        private Type exceptionType = null;

        #endregion

        #region Properties

        ///<summary>
        /// The type of <see cref="Exception"/> to match this policy entry to.
        ///</summary>
        public Type ExceptionType
        {
            get
            {
                return exceptionType;
            }
            private set
            {
                exceptionType = value;
            }
        }

        internal string PolicyName
        {
            set { policyName = value; }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the 
        /// <see cref="ExceptionPolicyEntry"/> class.
        /// </summary>
        /// <param name="exceptionType">Type of exception this policy refers to.</param>
        /// <param name="postHandlingAction">What to do after the exception is handled.</param>
        /// <param name="handlers">Handlers to execute on the exception.</param>
        public ExceptionPolicyEntry(
            Type exceptionType,
            PostHandlingAction postHandlingAction,
            IEnumerable<IExceptionHandler> handlers)
        {
            Contract.Requires<ArgumentNullException>(exceptionType != null, "exceptionType could not be null.");
            Contract.Requires<ArgumentNullException>(handlers != null, "handlers could not be null.");

            this.postHandlingAction = postHandlingAction;
            this.handlers = handlers;

            ExceptionType = exceptionType;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Handles all exceptions in the chain.
        /// </summary>
        /// <param name="exceptionToHandle">The <c>Exception</c> to handle.</param>
        /// <returns>Whether or not a rethrow is recommended.</returns>
        public bool Handle(Exception exceptionToHandle)
        {
            Contract.Requires<ArgumentNullException>(exceptionToHandle != null, "exceptionToHandle could not be null.");

            Guid handlingInstanceID = Guid.NewGuid();

            // Handles all exceptions in the chain.
            Exception chainException = ExecuteHandlerChain(
                exceptionToHandle,
                handlingInstanceID
            );

            return RethrowRecommended(chainException, exceptionToHandle);
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Rethrows the given exception.  
        /// Placed in a seperate method for
        /// easier viewing in the stack trace.
        /// </summary>
        /// <param name="chainException"></param>
        /// <param name="originalException"></param>
        /// <returns></returns>
        private Exception IntentionalRethrow(Exception chainException, Exception originalException)
        {
            if (chainException != null)
            {
                throw chainException;
            }

            Exception wrappedException = new ExceptionHandlingException("Unable to rethrow exception: The exception to throw is null.");
            //instrumentationProvider.FireExceptionHandlingErrorOccurred(
            //    ExceptionUtility.FormatExceptionHandlingExceptionMessage(policyName, wrappedException, chainException, originalException));

            return wrappedException;
        }

        private bool RethrowRecommended(Exception chainException, Exception originalException)
        {
            if (postHandlingAction == PostHandlingAction.None)
                return false;

            if (postHandlingAction == PostHandlingAction.ThrowNewException)
            {
                throw IntentionalRethrow(chainException, originalException);
            }
            return true;
        }

        private Exception ExecuteHandlerChain(Exception ex, Guid handlingInstanceID)
        {
            string lastHandlerName = String.Empty;
            Exception originalException = ex;

            try
            {
                foreach (IExceptionHandler handler in handlers)
                {
                    lastHandlerName = handler.GetType().Name;
                    ex = handler.HandleException(ex, handlingInstanceID);
                    //instrumentationProvider.FireExceptionHandlerExecutedEvent();
                }
            }
            catch (Exception/* handlingException*/)
            {
                //instrumentationProvider.FireExceptionHandlingErrorOccurred(
                //    ExceptionUtility.FormatExceptionHandlingExceptionMessage(
                //        policyName,
                //        new ExceptionHandlingException(string.Format(CultureInfo.CurrentCulture, Resources.UnableToHandleException, lastHandlerName), handlingException),
                //        ex,
                //        originalException));
                throw new ExceptionHandlingException(
                    string.Format(CultureInfo.CurrentCulture, "Unable to handle exception: '{0}'.", lastHandlerName));
            }

            return ex;
        }

        #endregion
    }
}
